﻿using log4net;
using System;
using Velocity4Net.Util;
using Velocity4Net.Util.Introspection;

namespace Velocity4Net.Runtime.Parser.Node
{

    /*
     * Licensed to the Apache Software Foundation (ASF) under one
     * or more contributor license agreements.  See the NOTICE file
     * distributed with this work for additional information
     * regarding copyright ownership.  The ASF licenses this file
     * to you under the Apache License, Version 2.0 (the
     * "License"); you may not use this file except in compliance
     * with the License.  You may obtain a copy of the License at
     *
     *   http://www.apache.org/licenses/LICENSE-2.0
     *
     * Unless required by applicable law or agreed to in writing,
     * software distributed under the License is distributed on an
     * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
     * KIND, either express or implied.  See the License for the
     * specific language governing permissions and limitations
     * under the License.
     */










    /**
     * Executor for setting public fields in objects
     *
     * @author <a href="mailto:henning@apache.org">Henning P. Schmiedehausen</a>
     * @author <a href="mailto:cdauth@cdauth.eu">Candid Dauth</a>
     */
    public class SetPublicFieldExecutor : SetExecutor
    {
        private readonly Introspector introspector;

        /**
         * Field to be accessed
         */
        private Field field = null;

        /**
         * @param log
         * @param introspector
         * @param clazz
         * @param property
         * @param arg
         */
        public SetPublicFieldExecutor(ILog log, Introspector introspector,
                Type clazz, String property, Object arg)
        {
            this.log = log;
            this.introspector = introspector;

            // Don't allow passing in the empty string or null because
            // it will either fail with a StringIndexOutOfBounds error
            // or the introspector will get confused.
            if (StringUtils.isNotEmpty(property))
            {
                discover(clazz, property, arg);
            }
        }

        public bool isAlive()
        {
            return getField() != null;
        }

        /**
         * @return The current field.
         */
        public Field getField()
        {
            return field;
        }

        /**
         * @param field
         */
        protected void setField(Field field)
        {
            this.field = field;
        }

        /**
         * @return The current introspector.
         */
        protected Introspector getIntrospector()
        {
            return this.introspector;
        }

        /**
         * @param clazz
         * @param property
         * @param arg
         */
        protected void discover(Type clazz, String property, Object arg)
        {
            try
            {
                Field field = introspector.getField(clazz, property);
                if (!Modifier.isFinal(field.getModifiers()))
                {
                    setField(field);
                }
            }
            /**
             * pass through application level runtime exceptions
             */
            catch (SystemException e)
            {
                throw e;
            }
            catch (Exception e)
            {
                String msg = "Exception while looking for public field '" + property;
                log.Error(msg, e);
                throw new VelocityException(msg, e);
            }
        }

        /**
         * Execute method against context.
         * @param o
         * @param value
         * @return The value of the invocation.
         * @throws IllegalAccessException
         * @throws InvocationTargetException
         */
        public Object execute(Object o, Object value)
        {
            if (isAlive())
            {
                Object oldValue = getField().get(o);
                getField().set(o, value);
                return oldValue;
            }
            else
                return null;
        }
    }
}